package com.ppl.server.modules;

import com.google.common.base.Strings;
import com.google.inject.AbstractModule;
import com.google.inject.ProvisionException;
import com.google.inject.TypeLiteral;
import com.google.inject.matcher.Matchers;
import com.google.inject.spi.InjectionListener;
import com.google.inject.spi.TypeEncounter;
import com.google.inject.spi.TypeListener;
import com.ppl.server.boot.config.Startup;

import javax.annotation.PostConstruct;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * Calls automatically methods annotated with {@link javax.annotation.PostConstruct} after injection.
 * 
 * TODO [nbe] The PreDestroy method is not called yet, there's no hook in guice for that.
 * 
 * @author Nicolas Berthet
 * @since Apr 11, 2012 11:39:10 AM
 */
public class LifecycleModule extends AbstractModule {

	/**
	 * Whether the {@link javax.annotation.PostConstruct} has to be handled the same way as
	 * {@link Startup}, in web context, due to JSR implementation,
	 * {@link javax.annotation.PostConstruct} may be called by the container, thus, it may be
	 * necessary to avoid calling these methods.
	 */
	private boolean usePostConstruct;
	
	public LifecycleModule() {
		this(true);
	}
	
	public LifecycleModule(boolean usePostConstruct) {
		this.usePostConstruct = usePostConstruct;
	}
	
	/* (non-Javadoc)
	 * @see com.google.inject.AbstractModule#configure()
	 */
	protected void configure() {
		// Creates a type listener that registers an injection listener on the
		// PostConstruct method
		bindListener(Matchers.any(), new TypeListener() {			
			@Override
			public <I> void hear(TypeLiteral<I> injectableType, TypeEncounter<I> encounter) {
				Class<? super I> type = injectableType.getRawType();
				
				for (Class<?> klass = type; klass != null && klass != Object.class; klass = klass.getSuperclass()) {
					
					Method[] methods = klass.getDeclaredMethods();
					for (final Method method : methods) {
						
						boolean isStartupMethod = method.isAnnotationPresent(Startup.class) ||
								(usePostConstruct && method.isAnnotationPresent(PostConstruct.class));
						
						// TODO [nbe] Identify and register shutdown methods (maybe only possible in singleton scope)
						
						if (isStartupMethod) {
							encounter.register(new MethodInvoker<I>(method));
						}
					}					
				}
			}
		});
	}

	private static class MethodInvoker<I> implements InjectionListener<I> {
		
		private Method method;
		
		public MethodInvoker(Method method) {
			this.method = method;
		}

		public void afterInjection(I injectee) {
			try {
				method.invoke(injectee);
			} catch (InvocationTargetException ie) {
				Throwable e = ie.getTargetException();
				throw new ProvisionException(Strings.nullToEmpty(e.getMessage()), e);
			} catch (IllegalAccessException e) {
				throw new ProvisionException(Strings.nullToEmpty(e.getMessage()), e);
			}
		}
	}
}